#ifndef GAMEENGINE_CORE_MEMORY_MEMORY_MANAGER_H
#define GAMEENGINE_CORE_MEMORY_MEMORY_MANAGER_H

#include <GameEngine/Core/core_settings.h>

#include <GameEngine/Core/Macros/macro_singleton.h>
#include <GameEngine/Core/Memory/allocator_default.h>


#ifdef GAMEENGINE_NAMESPACES
namespace GameEngine {
#endif

template <class T, class A> class PtrResource;


//========================================================


//! Manages dynamically allocated data
/*!
 *  This class takes care of dynamically allocated data, so that programmers can track
 *  the memory consumption. This is especially useful to detect memory leaks or to check
 *  if memory usage does not overwhelm the memory budget allowed for the program.
 *
 *  This class has two tracking options: it can either allocate/desallocate memory on the
 *  demand of client code, or simply reference/dereference memory that was allocated by an
 *  external piece of code. Both options are available at any time (they are not exclusive).
 *
 *  The memory manager has some limitations: (i) it can only track memory dynamically
 *  allocated on the CPU, but neglects others type of memory (e.g., GPU memory, static
 *  memory, etc.), and (ii) it handles memory only if specifically asked to do so by the
 *  client code.
 */
class _Core_decl MemoryManager {

GAMEENGINE_DECLARE_SINGLETON(MemoryManager)

public:
	//! Handles number of bytes
	typedef unsigned int BytesCount;
	//! Handles number of elements
	typedef unsigned int ElementsCount;

public:
	virtual ~MemoryManager();

	template <class T, class A> void reference(const PtrResource<T,A>& item);
	template <class T, class A> void dereference(const PtrResource<T,A>& item);

	template <class T> void reference(ElementsCount nb_elements = 1);
	template <class T> void dereference(ElementsCount nb_elements = 1);

	template <class T> void reference(const T* elements, ElementsCount nb_elements = 1);
	template <class T> void dereference(const T* elements, ElementsCount nb_elements = 1);

	template <class T> T* allocate(const T& rhs);
	template <class T> T* allocate(ElementsCount nb_elements = 1);
	template <class T> void desallocate(const T* elements, ElementsCount nb_elements = 1);

	template <class T, class A> T* allocate_special(ElementsCount nb_elements = 1);
	template <class T, class A> void desallocate_special(const T* elements, ElementsCount nb_elements = 1);

	BytesCount bytes() const;

private:
	MemoryManager();

private:
	//! Number of bytes currently used
	BytesCount bytes_;
};


//========================================================

//! Reference the given item
/*!
 @param item The item to reference
 */
template <class T, class A>
void MemoryManager::reference(const PtrResource<T,A>& item) {
	bytes_ += item.nb_elements_ * sizeof(T);
}

//! Dereference the given item
/*!
 @param item The item to dereference
 */
template <class T, class A>
void MemoryManager::dereference(const PtrResource<T,A>& item) {
	bytes_ -= item.nb_elements_ * sizeof(T);
}

//! Reference the given number of elements
/*!
 @param nb_elements The number of elements in the dynamic array of elements
 */
template <class T>
void MemoryManager::reference(ElementsCount nb_elements) {
	bytes_ += nb_elements * sizeof(T);
}

//! Dereference the given number of elements
/*!
 @param nb_elements The number of elements in the dynamic array of elements
 */
template <class T>
void MemoryManager::dereference(ElementsCount nb_elements) {
	bytes_ -= nb_elements * sizeof(T);
}

//! Reference the given dynamic array of elements
/*!
 @param elements The dynamic array of elements to reference
 @param nb_elements The number of elements in the dynamic array of elements
 */
template <class T>
void MemoryManager::reference(const T* elements, ElementsCount nb_elements) {
	bytes_ += nb_elements * sizeof(T);
}

//! Dereference the given dynamic array of elements
/*!
 @param elements The dynamic array of elements to dereference
 @param nb_elements The number of elements in the dynamic array of elements
 */
template <class T>
void MemoryManager::dereference(const T* elements, ElementsCount nb_elements) {
	bytes_ -= nb_elements * sizeof(T);
}

//! Allocate and reference one element using its copy constructor
/*!
 @param rhs The element to copy
 */
template <class T>
T* MemoryManager::allocate(const T& rhs) {
	T* elements = AllocatorDefault<T>::copy(rhs);
	reference(elements, 1);
	return elements;
}

//! Allocate and reference a dynamic array of elements
/*!
 @param nb_elements The number of elements in the dynamic array of elements
 */
template <class T>
T* MemoryManager::allocate(ElementsCount nb_elements) {
	T* elements = AllocatorDefault<T>::allocate(nb_elements);
	reference(elements, nb_elements);
	return elements;
}

//! Desallocate and dereference the given dynamic array of elements
/*!
 @param elements The dynamic array of elements to desallocate
 @param nb_elements The number of elements in the dynamic array of elements
 */
template <class T>
void MemoryManager::desallocate(const T* elements, ElementsCount nb_elements) {
	if ( elements == nil ) return;
	dereference(elements, nb_elements);
	AllocatorDefault<T>::desallocate(elements,nb_elements);
}

//! Allocate and reference a dynamic array of elements using a special allocator
/*!
 @param nb_elements The number of elements in the dynamic array of elements
 */
template <class T, class A>
T* MemoryManager::allocate_special(ElementsCount nb_elements) {
	T* elements = A::allocate(nb_elements);
	reference(elements, nb_elements);
	return elements;
}

//! Desallocate and dereference the given dynamic array of elements using a special allocator
/*!
 @param elements The dynamic array of elements to desallocate
 @param nb_elements The number of elements in the dynamic array of elements
 */
template <class T, class A>
void MemoryManager::desallocate_special(const T* elements, ElementsCount nb_elements) {
	if ( elements == nil ) return;
	dereference(elements, nb_elements);
	A::desallocate(elements,nb_elements);
}

//========================================================

#ifdef GAMEENGINE_NAMESPACES
} // namespace GameEngine
#endif

#endif
